﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cryptography.Interfaces;
using System.Security.Cryptography;
using System.IO;

namespace Cryptography.Managers
{
    public class CryptographyManager : ICryptographyManager
    {
        private readonly ICryptoTransform _encryptorTransform;
        private readonly ICryptoTransform _decryptorTransform;
        private readonly UTF8Encoding _utfEncoder;
        private readonly AesCryptoServiceProvider _csp;

        public CryptographyManager()
        {
            _utfEncoder = new UTF8Encoding();
            _csp = new AesCryptoServiceProvider();
        }

        public CryptographyManager(string key, string vector)
            : this()
        {
            var bKey = GetLegalKey(key);
            var bVector = GetLegalKey(vector);

            _encryptorTransform = _csp.CreateEncryptor(bKey, bVector);
            _decryptorTransform = _csp.CreateDecryptor(bKey, bVector);
        }


        public string Encrypt(string source)
        {
            if (string.IsNullOrEmpty(source))
                throw new ArgumentNullException("Entered string is not valid");

            var bytes = _utfEncoder.GetBytes(source);
            var encrypted = EncryptToByte(bytes);

            return Convert.ToBase64String(encrypted);
        }

        public string Decrypt(string source)
        {
            if (string.IsNullOrEmpty(source))
                throw new ArgumentNullException("Entered string is not valid");

            var bytes = Convert.FromBase64String(source);
            var decrypted = DecryptToByte(bytes);

            return _utfEncoder.GetString(decrypted);
        }

        public string GetHash(string source)
        {
            if (string.IsNullOrEmpty(source))
                throw new ArgumentNullException("Entered string is not valid");

            var bytes = _utfEncoder.GetBytes(source);
            var sha = new SHA512Managed();
            var hashBytes = sha.ComputeHash(bytes);

            return Convert.ToBase64String(hashBytes);
        }

        #region Helpers
        private byte[] DecryptToByte(byte[] bytes)
        {
            #region Write the encrypted value to the decryption stream
            var encryptedStream = new MemoryStream();
            var decryptStream = new CryptoStream(encryptedStream, _decryptorTransform, CryptoStreamMode.Write);
            decryptStream.Write(bytes, 0, bytes.Length);
            decryptStream.FlushFinalBlock();
            #endregion

            #region Read the decrypted value from the stream.
            encryptedStream.Position = 0;
            var decryptedBytes = new Byte[encryptedStream.Length];
            encryptedStream.Read(decryptedBytes, 0, decryptedBytes.Length);
            encryptedStream.Close();
            #endregion

            return decryptedBytes;
        }

        private byte[] EncryptToByte(byte[] bytes)
        {
            #region Write the decrypted value to the encryption stream
            var memoryStream = new MemoryStream();
            var cs = new CryptoStream(memoryStream, _encryptorTransform, CryptoStreamMode.Write);
            cs.Write(bytes, 0, bytes.Length);
            cs.FlushFinalBlock();
            #endregion

            #region Read encrypted value back out of the stream
            memoryStream.Position = 0;
            var encrypted = new byte[memoryStream.Length];
            memoryStream.Read(encrypted, 0, encrypted.Length);
            #endregion

            cs.Close();
            memoryStream.Close();

            return encrypted;
        }
        private byte[] GetLegalKey(string Key)
        {
            string sTemp;
            if (_csp.LegalKeySizes.Length > 0)
            {
                int moreSize = _csp.LegalKeySizes[0].MinSize;
                // key sizes are in bits

                while (Key.Length * 8 > moreSize)
                {
                    moreSize += _csp.LegalKeySizes[0].SkipSize;
                }
                sTemp = Key.PadRight(moreSize / 8, ' ');
            }
            else
                sTemp = Key;

            // convert the secret key to byte array
            return _utfEncoder.GetBytes(sTemp);
        }
        #endregion
    }
}
